﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using UnityEngine;

public class UDPSocket
{
    private Socket _client;
    private string _remoteHost;
    private int _remotePort;
    private IPEndPoint _remoteEP;
    private int _localPort;
    private bool _isStopThread = false;
    private Action<byte[]> _cbReceiveMessage = null;
    private Action<string> _cbReceiveMessageStr = null;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="localPort">本地Port</param>
    /// <param name="remoteHost">远程IP(目标主机IP)</param>
    /// <param name="remotePort">远程Port(目标主机Port)</param>
    /// <param name="cbReceiveMessage">接收消息回调</param>
    public UDPSocket(int localPort, string remoteHost, int remotePort, Action<byte[]> cbReceiveMessage)
    {
        _remoteHost = remoteHost;
        _remotePort = remotePort;
        _remoteEP = new IPEndPoint(IPAddress.Parse(_remoteHost), _remotePort);
        _localPort = localPort;
        _cbReceiveMessage = cbReceiveMessage;

        //绑定本地端口
        IPEndPoint localEP = new IPEndPoint(IPAddress.Any, _localPort);
        _client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        _client.Bind(localEP);
        Debug.Log("成功开启UDP");
        //异步接收消息
        Thread tdRecv = new Thread(OnRecvThread);
        tdRecv.IsBackground = true;
        tdRecv.Start();
        Debug.Log("开始接收数据");
    }


    /// <summary>
    /// 关闭数据
    /// </summary>
    public void Close()
    {
        _isStopThread = true;
        if (_client != null) _client.Close();
        _client = null;
    }



    private void OnRecvThread()
    {
        try
        {
            while (true)
            {
                if (_isStopThread)
                {
                    return;
                }

                byte[] buffer = new byte[1024*1024];
                EndPoint point = new IPEndPoint(IPAddress.Any, 0);//保存发送方的ip和端口号
                int length = _client.ReceiveFrom(buffer, ref point);//线程阻塞  接收消息
                if (_cbReceiveMessage != null)
                {
                    _cbReceiveMessage(buffer);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }



    /// <summary>
    /// 发送字符串
    /// </summary>
    /// <param name="message"></param>
    public void Send(string message)
    {
        byte[] data = Encoding.UTF8.GetBytes(message);//字符串与字节数组相互转换
        Send(data);
    }

    int sendIndex = 0;
    /// <summary>
    /// 发送数据流
    /// </summary>
    /// <param name="dataStream"></param>
    public void Send(byte[] dataStream)
    {
        List<UdpPackage> udpPackages = GetList(dataStream, sendIndex);
        sendIndex++;
        for (int i = 0; i < udpPackages.Count; i++)
        {
            string str = JsonUtility.ToJson(udpPackages[i]);
            Debug.Log("服务端消息为：" + str);
            byte[] data = Encoding.UTF8.GetBytes(str);
            _client.SendTo(data, _remoteEP); //发送信息
        }
        Debug.Log("发送数据");
    }


    //分包的方法，传入一个byte数组和包的编号（编号是用来判断收到数据是否是同一个包的）
    public List<UdpPackage> GetList(byte[] bytes, int num)
    {
        List<UdpPackage> packages = new List<UdpPackage>();//定义一个包集合
        int count = 0;//用来记录包在包集合中的位置
        int nums = bytes.Length / 1000;//分包后包集合中包的数量
        int bytecount = bytes.Length;
        if (bytes.Length % 1000 > 0) nums++;

        //循环遍历传入的byte数组，根据每个包中的二进制数组长度（Count）来分割
        for (int i = 0; i <= bytes.Length; i += 1000)
        {
            count++;//位置++

            byte[] bs = new byte[1000];
            int all = 1000;
            //判断最后一个分包是否超过剩余byte数组的长度
            //如果超过，则最后一个包的长度为剩余byte数组的长度
            if (i + 1000 > bytes.Length)
                all = bytes.Length - i;
            //将传入byte数组copy到包中
            Buffer.BlockCopy(bytes, i, bs, 0, all);
            packages.Add(new UdpPackage
            {
                Bytes = bs,
                PacketNum = num,
                PacketAtNum = count,
                PacketCount = nums,
                AllBytesCount = bytecount
            });
        }
        return packages;
    }

    
    List<UdpPackage> udps = new List<UdpPackage>();
    //组合包方法，传入一个包集合，和包的编号
    public byte[] GetBytes(UdpPackage package, int count)
    {
        byte[] bytes = null;//接包二进制集合
        udps.Add(package);//把传入的包add进包集合中

        //linq查询，查出包集合中所有和传入包编号拥有相同编号的包，并根据所在包
        //的位置排序
        var result = from n in udps
                     where n.PacketNum == count
                     orderby n.PacketAtNum
                     select n;

        //判断查出包的集合是否跟分包时的数量一样
        if (result.Count() == package.PacketCount)
        {
            bytes = new byte[5120000];//初始化接包二进制数组
            int jiShu = 0;//定义一个计数器

            //遍历所有查询结果中的包，把每个包中的二进制数组组合起来
            foreach (UdpPackage v in result)
            {
                //把当前循环中包的二进制集合copy到接包数组中，从jishu*Count开始
                v.Bytes.CopyTo(bytes, jiShu * 1000);
                //计数器++
                jiShu++;
            }

            //调用清包方法，清除所有组包完成的包
            clear(count);

        }

        return bytes;
    }

    //清除方法，根据传入的包编号删除所有包集合中拥有此包编号的包
    private void clear(int index)
    {
        for (int i = 0; i < udps.Count;)
        {
            //判读包的编号是否已传入的编号相同
            //如果相同则删除，否则进入下一个
            if (udps[i].PacketNum == index)
                udps.Remove(udps[i]);
            else
                i++;
        }
    }
}

[Serializable]
public class UdpPackage
{
    public byte[] Bytes;
    public int PacketNum; //包的编号
    public int PacketAtNum;
    public int PacketCount;//包的总数
    public int AllBytesCount;
}